// 2015年版权所有etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package cindex

import (
	"sync"
	"sync/atomic"

	"go.etcd.io/etcd/server/v3/storage/backend"
	"go.etcd.io/etcd/server/v3/storage/schema"
)

type Backend interface {
	BatchTx() backend.BatchTx
}

// ConsistentIndexer是一个包装consistentIndex的Get/Set/Save方法的接口。
type ConsistentIndexer interface {

	// ConsistentIndex返回当前执行项的一致索引。
	ConsistentIndex() uint64

	// SetConsistentIndex设置当前执行项的一致索引。
	SetConsistentIndex(v uint64, term uint64)

	// 必须调用UnsafeSave来保持tx上的锁。
	// 它将一致的索引保存到底层稳定存储。
	UnsafeSave(tx backend.BatchTx)

	// 设置可用的后端。用于ConsistentIndexer的BatchTx。
	SetBackend(be Backend)
}

// consistentIndex实现ConsistentIndexer接口。
type consistentIndex struct {
	// consistentIndex表示一致副本日志中某个条目的偏移量。
	// 它缓存“一致索引”键的值。
	// 通过原子访问，因此必须是64位对齐的。
	consistentIndex uint64
	// term表示一致副本日志中已提交项的RAFT项。
	// 通过原子访问，因此必须是64位对齐的。
	// 该值从v3开始一直保存在后端。5.
	term uint64

	// be用于初始读取一致性索引
	be Backend
	// 互斥保护be。
	mutex sync.Mutex
}

// NewConsistentIndex创建一个新的一致性索引。
// 如果'be'为nil，则必须在使用'ConsistentIndex（）'首次访问之前设置它（SetBackend）。
func NewConsistentIndex(be Backend) ConsistentIndexer {
	return &consistentIndex{be: be}
}

func (ci *consistentIndex) ConsistentIndex() uint64 {
	if index := atomic.LoadUint64(&ci.consistentIndex); index > 0 {
		return index
	}
	ci.mutex.Lock()
	defer ci.mutex.Unlock()

	v, term := schema.ReadConsistentIndex(ci.be.BatchTx())
	ci.SetConsistentIndex(v, term)
	return v
}

func (ci *consistentIndex) SetConsistentIndex(v uint64, term uint64) {
	atomic.StoreUint64(&ci.consistentIndex, v)
	atomic.StoreUint64(&ci.term, term)
}

func (ci *consistentIndex) UnsafeSave(tx backend.BatchTx) {
	index := atomic.LoadUint64(&ci.consistentIndex)
	term := atomic.LoadUint64(&ci.term)
	schema.UnsafeUpdateConsistentIndex(tx, index, term, true)
}

func (ci *consistentIndex) SetBackend(be Backend) {
	ci.mutex.Lock()
	defer ci.mutex.Unlock()
	ci.be = be
	// 后端更改后，第一次访问应该重新读取。
	ci.SetConsistentIndex(0, 0)
}

func NewFakeConsistentIndex(index uint64) ConsistentIndexer {
	return &fakeConsistentIndex{index: index}
}

type fakeConsistentIndex struct {
	index uint64
	term  uint64
}

func (f *fakeConsistentIndex) ConsistentIndex() uint64 { return f.index }

func (f *fakeConsistentIndex) SetConsistentIndex(index uint64, term uint64) {
	atomic.StoreUint64(&f.index, index)
	atomic.StoreUint64(&f.term, term)
}

func (f *fakeConsistentIndex) UnsafeSave(_ backend.BatchTx) {}
func (f *fakeConsistentIndex) SetBackend(_ Backend)         {}

func UpdateConsistentIndex(tx backend.BatchTx, index uint64, term uint64, onlyGrow bool) {
	tx.Lock()
	defer tx.Unlock()
	schema.UnsafeUpdateConsistentIndex(tx, index, term, onlyGrow)
}
